/* Copyright (c) 1992, 1993, 1994 NeXT Computer, Inc.  All rights reserved. 
 *
 * ATI.m - ATI display driver.
 *
 * HISTORY
 * 07 Oct 92	Joe Pasqua
 *      Created. 
 * 01 June 93	Mike Paquette
 *	Rewrite: Convert from Corsair device driver to general MACH32 chipset
 * 		driver. Add support for multiple DACs and CRT setups.
 *		Add 8 bit monochrome support.  Add support for non-Cosair
 *		implementations.
 *  7 July 1993	Derek B Clegg
 *	Cleaned up for external release.
 * Mon Aug 15 17:18:12 PDT 1994 James C. Lee
 *   add 8-bit color support by integrating Peter Graffagnino's mods
 */

#import <driverkit/generalFuncs.h>
#import <driverkit/i386/ioPorts.h>
#import <driverkit/i386/directDevice.h>
#import <driverkit/i386/IOEISADeviceDescription.h>
#import <ansi/string.h>
#import "ATI.h"

#define	ONE_MEG	(1024 * 1024)

@implementation ATI

static void
UnlockShadowSet(int set)
{
    outb(SHADOW_SET, 2);
    outb(SHADOW_CNTL, 0);
    outb(SHADOW_SET, 1);
    outb(SHADOW_CNTL, 0);
    outb(SHADOW_SET, set);
}

static void
LockShadowSet(void)
{
    outb(SHADOW_SET, 1);
    outb(SHADOW_CNTL, 0x3F);
    outb(SHADOW_SET, 2);
    outb(SHADOW_CNTL, 0x3F);
    outb(SHADOW_SET, 0);
}

static void
ProgramShadowSet(int set, const ATI_CRTCSetup *setup)
{
    UnlockShadowSet(set);
	
    outb(DISP_CNTL, 0x53);		/* Reset, blanking display. */
    outb(H_TOTAL, setup->h_total);
    outb(H_DISP, setup->h_disp);
    outb(H_SYNC_START, setup->h_sync_start);
    outb(H_SYNC_WIDTH, setup->h_sync_wid);
    outw(V_TOTAL, setup->v_total);
    outw(V_DISP, setup->v_disp);
    outw(V_SYNC_START, setup->v_sync_start);
    outb(V_SYNC_WID, setup->v_sync_wid);
    outb(DISP_CNTL, setup->disp_cntl);
    outw(CLOCK_SELECT, setup->clock_select);

    /* Zero overscan registers to avoid weird borders. */
    outw(HORIZONTAL_OVERSCAN, 0);
    outw(VERTICAL_OVERSCAN, 0);
    outb(OVERSCAN_COLOR_BLUE, 0);
    outb(OVERSCAN_COLOR_GREEN, 0);
    outb(OVERSCAN_COLOR_RED, 0);

    LockShadowSet();
}

static void
SelectShadowSet(int set)
{
    unsigned char v;

    switch (set) {
    case 0:
	v = 2;
	break;
    case 1:
	v = 3;
	break;
    case 2:
	v = 7;
	break;
    default:
	return;
    }
    outb(ADVFUNC_CNTL, v);
}

static void
SetMemOffset(void)
{
    unsigned int vga_boundary;
	
    /* Get the video memory boundary, in 256K pages.  The 8514 controller
     * cannot access memory below this, and the VGA controller cannot
     * access memory above this. */
    vga_boundary = inb(MEM_BNDRY) & 0x0f;
	
    /* Set the start of the CRT buffer to match the start of the
     * 8514 controller VRAM address.  We also set up the graphics engine... */
    outb(GE_OFFSET_HI, vga_boundary);
    outb(CRT_OFFSET_HI, vga_boundary);
    outb(GE_OFFSET_LO, 0);
    outb(CRT_OFFSET_LO, 0);
}

/*
 * Returns the physical base address of the memory aperature.  The method
 * used to decode this varies based on the BIOS and Mach32 chip type.
 */
static unsigned int
MemoryAperatureBaseAddress(void)
{
	unsigned char *ip;
	unsigned int location;	/* Base address in Mbytes */
	
	/*
	 * The PC BIOS is broken into 2 Kb slices. On startup, the ATI BIOS
	 * stashes away which 2 Kb slice it live in in scratch reg 0.
	 */
	ip = (unsigned char *)
	    (((inb(ROM_SCRATCH_PAD_0) & 0x7F) * 0x800) + ATI_BIOS_BASEADDR);

	/* Look up the BIOS flag to see if it uses extended or simple format */
	if ( (ip[0x62] & 1) != 0 )	/* Corsair/Nova style BIOS */
	{
		/*
		 * MEM_CFG+1 specifies an address from 0-127 Mb
		 * (MSB is always 0), and the scratch register specifies
		 * what the external hardware decodes, up to 4 Gb.
		 */
		location =   ((inb(ROM_SCRATCH_PAD_0 + 1) & 0x1F) << 7)
			   | (inb(MEM_CFG + 1));
	}
	else	/* No external address decode.  Just use MEM_CFG register */
	{
		if ((inb(CONFIG_STATUS_2 + 1) & MEM_APERATURE_4GB_RANGE) != 0)
			location = inw( MEM_CFG ) >> 4;
		else
			location = inb( MEM_CFG + 1 );
	}
#if DEBUG
	IOLog("MemoryAperatureBaseAddress() == 0x%x\n", location * ONE_MEG);
#endif
	return (location * ONE_MEG); /* Return phys address in bytes, not Mb */
}

/*
 * Returns the physical base address of the memory aperature.  The method
 * used to decode this varies based on the BIOS and Mach32 chip type.
 */
static BOOL
SetMemoryAperatureBaseAddress(unsigned int newLocation)
{
	unsigned char *ip;
	unsigned int location;
#if DEBUG
	IOLog("SetMemoryAperatureBaseAddress(0x%x)\n", newLocation );
#endif
	/* Convert location to Mbytes. Make sure it's on a 1 Mb boundry */
	if ( (newLocation & (ONE_MEG - 1)) != 0 )
		return NO;
	location = newLocation / ONE_MEG;
	
	/*
	 * The PC BIOS is broken into 2 Kb slices. On startup, the ATI BIOS
	 * stashes away which 2 Kb slice it live in in scratch reg 0.
	 */
	ip = (unsigned char *)
	    (((inb(ROM_SCRATCH_PAD_0) & 0x7F) * 0x800) + ATI_BIOS_BASEADDR);

	/* Look up the BIOS flag to see if it uses extended or simple format */
	if ( (ip[0x62] & 1) != 0 )	/* Corsair/Nova style BIOS */
	{
		/*
		 * It's never safe to reprogram these, as we don't know
		 * how to set the off-chip decoding logic.
		 */
		 return NO;
	}
	else	/* No external address decode.  Just use MEM_CFG register */
	{
		if ((inb(CONFIG_STATUS_2 + 1) & MEM_APERATURE_4GB_RANGE) != 0)
		{
			outw(MEM_CFG, (location << 4)|(inw( MEM_CFG ) & 0xF));
		}
		else
		{
			if ( newLocation > ATI_LOCALBUS_VRAM_ADDRESS )
				return NO; /* Invalid addr for this config */
			outb( MEM_CFG + 1, location );
		}
	}
#if DEBUG
	IOLog("SetMemoryAperatureBaseAddress(0x%x) succeeds\n", newLocation );
#endif
	return YES;
}

/* Undo our munging of the DAC.  Put DAC back in a state usable by VGA mode.
 */
static void
reset_DAC(void)
{
    ATI_DAC dac_type;

    /* Disable VGA passthrough mode so the DAC may be programmed. */
    outw(CLOCK_SELECT, inw(CLOCK_SELECT) | 1);

    dac_type = (inb(CONFIG_STATUS_1 + 1) >> 1) & 7;
    switch(dac_type) {
    case ATI_DAC_ATT20C491:		/* ATT 20C491 */
    case ATI_DAC_Bt481:			/* Brooktree Bt481, Bt482 */
	/* TO DO: Not tested yet */
	outw(EXT_GE_CONFIG, 0x101A);	/* DAC reg 1. */
	outb(DAC_MASK, 0);		/* VGA mode. */
	break;

    case ATI_DAC_68875:			/* ATI or TI 34075. */
	outw(EXT_GE_CONFIG, 0x201A);	/* DAC reg 1. */
	outb(INPUT_CLK_SEL, 0);		/* clock 0. */
	outb(OUTPUT_CLK_SEL, 0);	/* SCLK & VCLK 1. */
	outb(MUX_CNTL, 0x2D);		/* MUX CNTL to 8/16. */

	/* Set default 8 BPP delay and blank adjust. */
	outw(LOCAL_CNTL, (inw(LOCAL_CNTL) | 0x8));
	/* Set PIXEL DELAY = 3, BLANK ADJ = 0	(0xC). */
	outb(MISC_CNTL + 1, (inb(R_MISC_CNTL + 1) & 0xF0) | 0xC);
	/* Set horizontal skew. */
	outw(HORIZONTAL_OVERSCAN, 1);
	break;

    case ATI_DAC_68830:			/* ATI 68830*/
    default:
	/* Set PIXEL DELAY = 0, BLANK ADJ = 0. */
	outb(MISC_CNTL + 1, (inb(R_MISC_CNTL + 1) & 0xF0) | 0);
	/* Set horizontal skew. */
	outw(HORIZONTAL_OVERSCAN, 0);
	break;
    }

    /* Put DAC in 6 bit mode, engine in 8 bit mode. */
    outw(EXT_GE_CONFIG, 0x001A);	/* reset EXT_DAC_ADDR */
    /* Enable VGA passthrough mode. */
    outw(CLOCK_SELECT, inw(CLOCK_SELECT) & ~1);
}

static void
program_TI34075(const ATI_DACSetup *parm, const IODisplayInfo *displayMode)
{
    ATI_CRTCSetup *setup;

    /* DAC RS 3 active, 8 bit pixel width. */
    outw(EXT_GE_CONFIG, 0x201A);
    outb(OUTPUT_CLK_SEL, parm->out_clk);
    outb(MUX_CNTL, parm->mux);
    outb(INPUT_CLK_SEL, parm->in_clk);
    outw(EXT_GE_CONFIG, parm->GE_config);
    outb(DAC_MASK, parm->mask);

    /* Set blank adjust and pixel delay values per parm->delay_timing. */
    outb(MISC_CNTL + 1, (inb(R_MISC_CNTL + 1) & 0xf0) | parm->delay_timing);

    setup = (ATI_CRTCSetup *)displayMode->parameters;
    if (setup->mux_flag == 0)
	return;

    /* Set PIXEL DELAY = 0, BLANK ADJ = 1. */
    outb(MISC_CNTL + 1, (inb(R_MISC_CNTL + 1) & 0xF0) | 1);

    UnlockShadowSet(2);
    outw(CLOCK_SELECT, setup->clock_select);
    LockShadowSet();
    SelectShadowSet(2);
}

static void
program_ATI68830(const ATI_DACSetup *parm, const IODisplayInfo *displayMode)
{
    /* Set pixel depth, packing, aliased monitor ID of 2. */
    outw(EXT_GE_CONFIG, parm->GE_config & 0x0fff);
}

/* Brooktree Bt481, Bt482 or ATT 20C491 RAMDAC setup code.
 * On entry, the CRT controller is programmed, and VGA passthru is disabled.
 * TO DO:  Test with ATT 20C491.
 */
static void
program_Bt481(const ATI_DACSetup *parm, const IODisplayInfo *displayMode)
{
    /* The clock should already be set appropriately for a 'slow', or
     * double-clocked DAC on entry. */

    /* Activate the TrueColor mode in the DAC. */

    /* DAC RS 2 active, 8 bit pixel width. */
    outw(EXT_GE_CONFIG, 0x101A);

    /* Set reg 6 to correct mode. */
    outb(DAC_MASK, parm->mode);
	
    /* Set blank adjust and pixel delay values. */
    outb(MISC_CNTL + 1, (inb(R_MISC_CNTL + 1) & 0xf0) | parm->delay_timing);

    /* Set pixel depth, packing, aliased monitor ID of 2. */
    outw(EXT_GE_CONFIG, parm->GE_config & 0x0fff);

    /* Set the appropriate DAC pixel mask. */
    outb(DAC_MASK, parm->mask);
}

/* Set up a simple gamma corrected grayscale. We rely on the Window Server
 * invoking this function once via IO_SET_TRANSFER_TABLE during startup.
 */
static void
ATISetTransferTable(const unsigned int *table, const IODisplayInfo *display, 
		   int brightness)
{
    ATI_DAC dac_type;
    int g, val;
    unsigned int scale;

    /* 
     * can only adjust ramdac for 8bit gray and 8bit color
     */
    if (display == 0 || display->bitsPerPixel != IO_8BitsPerPixel || 
	table == 0)
	return;
    dac_type = (inb(CONFIG_STATUS_1 + 1) >> 1) & 7;

    switch (dac_type) {
    case ATI_DAC_68875:		/* ATI or TI 34075 */
    case ATI_DAC_68830:		/* ATI 68830*/
    case ATI_DAC_Bt476:
	scale = 0;
	break;
    case ATI_DAC_ATT20C491:	/* ATT 20C491 */
    case ATI_DAC_Bt481:		/* Brooktree Bt481, Bt482 */
    default:
	scale = 2;
	break;
    }

    outb(DAC_W_INDEX, 0);

    if (display->colorSpace == IO_RGBColorSpace ) {
	for (g = 0; g < IO_8BPP_TRANSFER_TABLE_SIZE; g++) {
	    val = (table[g] >> 24);
	    if (scale)
		val >>= scale;
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	    val = ((table[g] & 0xFF0000) >> 16);
	    if (scale)
		val >>= scale;
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	    val = ((table[g] & 0xFF00) >> 8);
	    if (scale)
		val >>= scale;
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	}
    } else {
	for (g = 0; g < IO_8BPP_TRANSFER_TABLE_SIZE; g++) {
	    val = (table[g] & 0x000000FF);
	    if (scale)
		val >>= scale;
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	    outb(DAC_DATA, EV_SCALE_BRIGHTNESS(brightness, val));
	}
    }
}

- (void)setupHardware
{
    ATI_DAC dac_type;
    const ATI_DACSetup *parm;

    SetMemOffset();	/* Set VGA VRAM memory offset. */

    dac_type = (inb(CONFIG_STATUS_1 + 1) >> 1) & 7;

    /* Load a register shadow set with our desired setup. */

    /* Load CRT regs, and enable controller in appropriate mode, unblanking
     * the display in the process. */
    ProgramShadowSet(2, CRTControllerSetup);

    /* Select our setup, re-enabling the CRT controller. */
    SelectShadowSet(2);

    /* At this point, the CRT timing is set, VGA passthrough is disabled,
     * and the pixel clock is set.  We need to place the DAC in the correct
     * mode of operation. */

    if (displayMode->bitsPerPixel == IO_15BitsPerPixel) 
	parm = &ATI_DAC_Setup_15BPP;
    else
	parm = &ATI_DAC_Setup_8BPP;

    switch (dac_type) {
    case ATI_DAC_68830:		/* ATI 68830*/
	program_ATI68830(parm, displayMode);
	outw(HORZ_OVERSCAN, 1);
	break;

    case ATI_DAC_ATT20C491:	/* ATT 20C491 */
    case ATI_DAC_Bt481:		/* Brooktree Bt481, Bt482 */
	program_Bt481(parm, displayMode);
	break;

    case ATI_DAC_68875:		/* ATI or TI 34075 */
	program_TI34075(parm, displayMode);
	break;

    default:
	/* Bt476 or some other unusable part. */
	IOLog("%s: Unknown DAC on ATI board - ID:%d\n", [self name], dac_type);
	break;
    }

    /* Set the CRT pitch, in units of (pixels_per_line/8).
     * CAUTION: Writing ADVFUNC_CNTL or MEM_CNTL (0xbee8) resets
     * this to 1024 pixels/line. */
    outb(CRT_LINE_PITCH, CRTControllerSetup->xres >> 3);

    /* Also set the graphics engine... */
    outb(GE_PITCH, CRTControllerSetup->xres >> 3);
}

- (vm_offset_t)prepareMemoryAperture
{
    unsigned int physLoc;
    vm_offset_t virtLoc;
    vm_size_t length, needed_length;
    ATI_DAC dac_type;
    int mode, k;
    BOOL valid[ATIModeCount];
    BOOL slowDAC = NO;
    IORange *range;
    const IODisplayInfo *displayTable;
    int	numTableEntries;
    const IODisplayInfo *displayDefault;
    
    /* Set memory boundary to share. */
    outb(MEM_BNDRY, 0);									  
    /* Enable memory aperture with a 4 Mb width, page sel for page 0. */
    outb(MEM_CFG, (inb(MEM_CFG) & 0xf0) | 0x02);

    /* Get memory aperture location (units in Mbyte).  This is hardware
     * and version dependent.  The high 8 bits of MEM_CFG specify the
     * memory aperature location between 0 and 127Mb, in 1 Mb units.
     */
    range = [[self deviceDescription] memoryRangeList];
    if (range == NULL) {
	IOLog("%s: No memory range set.\n", [self name]);
	return 0;
    }

    physLoc = 0;	/* Default to Configure app settings. */

    /* Use the Misc Options register (36EE) bits 2&3 to determine
     * the amount of installed memory. 0 = 512, 1 = 1M, 2 = 2M, 3 = 4M.
     * Select a timing setup based on available memory and DACs.
     * We do this here instead of in goLinear so we can report the
     * potential frame buffer size early in the startup sequence.
     */
    dac_type = (inb(CONFIG_STATUS_1 + 1) >> 1) & 7;

    switch (dac_type) {
    case ATI_DAC_68830:		/* ATI 68830*/
    case ATI_DAC_68875:		/* ATI or TI 34075 */
	slowDAC = NO;
	break;
    case ATI_DAC_ATT20C491:	/* ATT 20C491 */
    case ATI_DAC_Bt476:		/* Brooktree Bt476, Bt478 */
    case ATI_DAC_Bt481:		/* Brooktree Bt481, Bt482 */
    default:
	/* These DACs can't handle the clock rates at 1024x768. */
	slowDAC = YES;
	break;
    }

    switch ((inb(MISC_OPTIONS) & 0x0c) >> 2) {
    default:
    case 0:				/* 512 Kb VRAM. */
	return (vm_offset_t)0;
    case 1:				/* 1 Mb VRAM. */
	length = ONE_MEG;
	break;
    case 2:				/* 2 Mb VRAM. */
    case 3:				/* 4 Mb VRAM. */
	length = 2 * ONE_MEG;		/* The most we currently need. */
	break;
    }
    
    switch (ati_flavor) {
    case ATI_EISA_Card:		/* Definitely programmable address. */
    case ATILocalBusCard:	/* Usually a programmable address. */
	/* Read out the full address using the mechanism endorsed by
	 * ATI.  This mechanism is supported by ATI in their Windows
	 * driver, and every vendor we've tested follows it rather than
	 * rolling their own drivers.
	 */
	physLoc = MemoryAperatureBaseAddress();

	/* If the address is in a range supported by the Mach32
	 * chipset (below 128 Mb), try to program it. Otherwise,
	 * we'll assume the address is hardwired, and pick it up
	 * using the ATI endorsed addressing mechanism. */
	if ( physLoc != range->start )
		SetMemoryAperatureBaseAddress( range->start );
	/* FALL THRU */

    default:			/* Unknown device. */
    case ATI_NovaCard:		/* Not programmable in prototypes tested. */
    case ATICorsair:		/* Not programmable. */
	/* Read out the full address using the mechanism endorsed by
	 * ATI.  This mechanism is supported by ATI in their Windows
	 * driver, and every vendor we've tested follows it rather than
	 * rolling their own drivers.
	 */
	physLoc = MemoryAperatureBaseAddress();

	/* If the address doesn't match the one set in Configure, complain.
	 * We then override the Configure address with the address reported
	 * by the hardware.  This gives the driver a fighting chance of
	 * working correctly, and works around a bug in 3.1 Configure and
	 * DriverKit which prevents setting addresses in the high 2 Gb of
	 * address space.
	 */
	if (range->start != physLoc || range->size < length) {
	    IOLog("%s: FB addr decodes as 0x%x, overriding configured "
		  "addr 0x%x.\n", [self name], physLoc, range->start);

	    /* Force the range to match the hardware config,
	     * so the Window Server will work correctly. */
	    range->start = physLoc;
	    range->size = length;
	    /* Reprogram the device description with usable values. */
	    [[self deviceDescription] setMemoryRangeList:range
	 	num:[[self deviceDescription] numMemoryRanges]];
	}
	break;
    }
     
    /* Enable kernel access with the appropriate mapping. */

    virtLoc = [self mapFrameBufferAtPhysicalAddress:physLoc length:length];
    if (virtLoc == 0)
        return virtLoc;

    /* If the DAC is a slow double-clocked part (ATT20C491, Bt481) use
     * the special setup for the part. */

    if (slowDAC == YES) {
    	displayTable = ATISlowDACMode;
	numTableEntries = ATISlowDACModeCount;
	displayDefault = &ATISlowDACMode[ATI_DEFAULT_SlowDAC_MODE];
    } else {
    	displayTable = ATIMode;
	numTableEntries = ATIModeCount;
	displayDefault = (length > ONE_MEG) ? &ATIMode[ATI_DEFAULT_2MEG_MODE]
					    : &ATIMode[ATI_DEFAULT_1MEG_MODE];
    }

    /* Determine which entries are valid for the amount of memory in the
     * system. */

    for (k = 0; k < numTableEntries; k++) {
    	needed_length = displayTable[k].width * displayTable[k].height;
	if (displayTable[k].bitsPerPixel != IO_8BitsPerPixel)
	    needed_length *= sizeof(short);
	valid[k] = (needed_length <= length);
	/* The 68830 DAC can't handle higher res than 1024x768. */
	if ( dac_type == ATI_DAC_68830 && displayTable[k].width > 1024 )
		valid[k] = NO;
    }

    /* Look up the correct entry to use. */
    mode = [self selectMode:displayTable count:numTableEntries valid:valid];
    if (mode < 0)
	displayMode = displayDefault;
    else
	displayMode = &displayTable[mode];
    CRTControllerSetup = (ATI_CRTCSetup *)displayMode->parameters;  
    return virtLoc;
}

- (void)establishLinearFB
{ 
    /* Local Bus timing magic */
    if ( inw( MACH32_STEP6_ID_REG ) != MACH32_STEP6_ID_VALUE )
    {
	asm volatile(
		     "pushl	%eax;		\n"
		     "pushl	%edx;		\n"
		     ";				\n"
		     "movl	0x01CE, %edx;	\n"
		     "movl	0xAE,	%eax;	\n"
		     "outb	%al,	%dx;	\n"
		     "incl	%edx;		\n"
		     "inb	%dx,	%al;	\n"
		     "andb	$ ~0x10, %al;	\n"
		     "movb	%al,	%ah;	\n"
		     "movb	$0xAE,	%al;	\n"
		     "decl	%edx;		\n"
		     "outw	%ax,	%dx;	\n"
		     ";				\n"
		     "movl	$0x2185,%eax;	\n"
		     "outw	%ax,%dx;	\n"
		     ";				\n"
		     "movl	$0x01CE, %edx;	\n"
		     "movl	$0xAE,	%eax;	\n"
		     "outb	%al,	%dx;	\n"
		     "incl	%edx;		\n"
		     "inb	%dx,	%al;	\n"
		     "orb	$ 0x10, %al;	\n"
		     "movb	%al,	%ah;	\n"
		     "movl	$0xAE,  %al;	\n"
		     "decl	%edx;		\n"
		     "outw	%ax,	%dx;	\n"
		     ";				\n"
		     "popl	%edx;		\n"
		     "popl	%eax;		\n");
    };

    /* Program controller to 16bpp or 8bpp mode. */
    [self setupHardware];
}

/* Determine whether there is an ATi chipset present.
 *   1) Check for an EISA card.  If found, we're done.
 *   2) Check for EISA CPU board ID.  If it's a Corsair, we're done.
 *   3) Fallback: assume a localbus+ISA card. (No ID mechanism!)
 */
- (boolean_t)ATIPresent
{
    int slot;
    unsigned int product_id;
    
    /* First, scan for a board in an EISA slot. */
    if ([self isEISAPresent] == TRUE) {
	for (slot = 1; slot < 16; slot++) {
	    if ([self getEISAId:&product_id forSlot:slot] == TRUE) {
	    	switch (product_id & ~0x0f) {
		case ATI_EISA_ID:
		    ati_flavor = ATI_EISA_Card;
		    return TRUE;
		case ATI_NOVA_PBUS_EISA_ID:
		    ati_flavor = ATI_NovaCard;
		    return TRUE;
		}
	    }
	}

	/* Examine the EISA ID for the motherboard, found in 'slot 0'.
	 * Do this AFTER the slot scan, so we preferentially use boards
	 * in the EISA slots.
	 */
	if ([self getEISAId:&product_id forSlot:0] == TRUE 
	    && (product_id & ~0x0f) == INTEL_CORSAIR_ID) {
	    ati_flavor = ATICorsair;
	    return TRUE;
	}
    }

    IOLog("%s: Assuming a local bus ATI card.\n", [self name]);
    ati_flavor = ATILocalBusCard;
    return TRUE;
}

- (void)enterLinearMode
{
    [self establishLinearFB];
}

- initFromDeviceDescription:deviceDescription
{
    void *frameBuffer;
    IODisplayInfo *display;
    
    if ([super initFromDeviceDescription:deviceDescription] == nil)
	return [super free];

    if (![self ATIPresent])
	return [super free];

    display = [self displayInfo];
    frameBuffer = (void *)[self prepareMemoryAperture];
    if (frameBuffer == 0)
        return nil;
    *display = *displayMode;
    display->frameBuffer = frameBuffer;
    display->flags = IO_DISPLAY_CACHE_WRITETHROUGH;
    if (displayMode->bitsPerPixel == IO_15BitsPerPixel) {
	display->flags |=  IO_DISPLAY_NEEDS_SOFTWARE_GAMMA_CORRECTION;
    } else if (displayMode->bitsPerPixel == IO_8BitsPerPixel) {
    	display->flags |= IO_DISPLAY_HAS_TRANSFER_TABLE;
    }
    return self;
}

- free
{
    return [super free];
}

- (void)revertToVGAMode
{
    /* Select VGA setup, re-enabling the VGA CRT controller. */
    SelectShadowSet(0);		/* Select VGA CRT configuration. */
    reset_DAC();		/* Restore DAC for VGA operation. */
    [super revertToVGAMode];	/* Let superclass do generic VGA stuff. */
}


/* Set the transfer tables.
 */
- setTransferTable:(const unsigned int *)table count:(int)numEntries
{
    if (transferTable != 0)
	IOFree(transferTable, sizeof(unsigned int)* transferTableCount);

    transferTableCount = numEntries;

    transferTable = IOMalloc(sizeof(unsigned int) * numEntries);
    bcopy(table, transferTable, sizeof(unsigned int) * numEntries);
    ATISetTransferTable(table, [self displayInfo], EV_SCREEN_MAX_BRIGHTNESS);
    return self;
}

/* Set the brightness to `level'.
 */
- setBrightness:(int)level token:(int)t
{
    if (level < EV_SCREEN_MIN_BRIGHTNESS || level > EV_SCREEN_MAX_BRIGHTNESS) {
	IOLog("%s Invalid brightness level `%d'.\n", [[self class] name],
							 level);
	return nil;
    }
    brightnessLevel = level;
    ATISetTransferTable(transferTable, [self displayInfo], brightnessLevel);
    return self;
}

@end
